package org.codetranslators.compiler.intermrep;

import org.codetranslators.common.Label;
import org.codetranslators.common.Temp;
import org.codetranslators.compiler.abstractassembly.*;

import java.util.Vector;

public class CodeGenerator 
{
	private Vector instructionsList;
	public static final String tab = "\t";
	public static final String newLine = "\r\n";
	
	public CodeGenerator()
	{
		instructionsList = new Vector();
	}
	
	/*public void generateCode(Vector stmLists)
	{
		Vector instructionsVector = new Vector();
		int numLists = stmLists.size();
		for(int i = 0; i < numLists; i++)
		{
			MipsAssemStmList list = (MipsAssemStmList) stmLists.elementAt(i);
		}
	}*/
	
	public void generateCode(AssemStmList list)
	{
		
		for(AssemStmList tempList = list; tempList != null; 
							tempList = tempList.getStmList())
		{
			AssemStm stm = tempList.getStm();
			if(stm != null)
				generateCode(stm);
		}
	}
	
	private void generateCode(AssemStm stm)
	{
		int stmType = stm.getStmType();
		if(stmType == AssemStm.MOVE)
		{
			AssemMove assemMove = (AssemMove) stm;
			AssemExp dstExp = assemMove.getDst();
			AssemExp srcExp = assemMove.getSrc();
			int dstExpType = dstExp.getExpType();
			if(dstExpType == AssemExp.MEM)
			{
				// The destination is a memory address
				AssemMemory dstMemory = (AssemMemory) dstExp;		
				AssemExp memChild = dstMemory.getMemExp();
				int memChildType = memChild.getExpType();
				if(memChildType == AssemExp.BIN_OP)
				{
					AssemBinOpExp opExp = (AssemBinOpExp) memChild;
					munchMoveMemBinOpDstStm(opExp, srcExp);
				}
				else if(memChildType == AssemExp.TEMP)
				{
					AssemTemp assemTemp = (AssemTemp) memChild;
					munchMoveMemTempDstStm(assemTemp.getTemp(), srcExp);
				}
				else if(memChildType == AssemExp.CONST)
				{
					AssemConst assemConst = (AssemConst) memChild;
					munchMoveMemConstDstStm(assemConst, srcExp);
				}
				else if(memChildType == AssemExp.CALL)
				{
					AssemCallExp callExp = (AssemCallExp) memChild;
					munchMoveMemCallDstStm(callExp, srcExp);
				}
				else if(memChildType == AssemExp.MEM)
				{
					AssemMemory memExp = (AssemMemory) memChild;
					munchMoveMemMemDstStm(memExp, srcExp);
				}
			}
			else
			{
				// The only other possible destination of a move is a temp
				AssemTemp assemTemp = (AssemTemp) dstExp;
				munchMoveTempDestStm(assemTemp.getTemp(), srcExp);
			}
		}
		else if(stmType == AssemStm.CJUMP)
		{
			AssemCJump cjump = (AssemCJump) stm;
			munchCJumpStm(cjump);
		}
		else if(stmType == AssemStm.JUMP)
		{
			AssemJump jump = (AssemJump) stm;
			munchJumpStm(jump);
		}
		else if(stmType == AssemStm.LABEL)
		{
			AssemLabel lbl = (AssemLabel) stm;
			munchLblStm(lbl);
		}
	}
	
	private void munchMoveMemBinOpDstStm(AssemBinOpExp opExp, AssemExp srcExp)
	{
		AssemExp opLeftChild = opExp.getLeft();
		AssemExp opRightChild = opExp.getRight();
		
		int leftChildType = opLeftChild.getExpType();
		int rightChildType = opRightChild.getType();
		
		int op = opExp.getOperator();
		if(op == AssemBinOpExp.PLUS || op == AssemBinOpExp.MINUS)
		{
			if(leftChildType == AssemExp.TEMP)
			{
				AssemTemp assemTemp = (AssemTemp) opLeftChild;
				Temp temp = assemTemp.getTemp();
				if(opRightChild.getExpType() == AssemExp.CONST)
				{
					// Found a  tile -> Move(Mem(BinOp(Plus/Minus, TEMP, i)), e1)
					AssemConst assemConst = (AssemConst) opRightChild;
					emitMoveMemOpTempConst(srcExp, opExp.getOperator(),
							assemConst.getValue(), temp);
				}
				else
				{
					//	The right child is not a const
					Temp rhsTemp = null;
					if(leftChildType == AssemExp.CALL) 
						rhsTemp = munchCallExp((AssemCallExp) opRightChild);
					else if(leftChildType == AssemExp.BIN_OP) 
						rhsTemp = munchBinOpExp((AssemBinOpExp) opRightChild);
					else if(leftChildType == AssemExp.MEM) 
						rhsTemp = munchMemoryExp((AssemMemory) opRightChild);
					else if(leftChildType == AssemExp.TEMP) 
						rhsTemp = munchTempExp((AssemTemp) opRightChild);
					
					Temp newTemp = addOrSubtractTemps(op, assemTemp.getTemp(), rhsTemp);						
					munchMoveMemTempDstStm(newTemp, srcExp);	
				}
			}
			else if(leftChildType == AssemExp.CONST)
			{
				AssemConst assemConst = (AssemConst) opLeftChild;
				int num = assemConst.getValue();
				if(rightChildType == AssemExp.TEMP)
				{
					AssemTemp assemTemp = (AssemTemp) opRightChild;
					Temp temp = assemTemp.getTemp();
					if(op == AssemBinOpExp.PLUS )
					{
						// Found a  tile -> Move(Mem(BinOp(Plus, i, TEMP)), e1)								
						emitMoveMemOpTempConst(srcExp, opExp.getOperator(),
								assemConst.getValue(), temp);
					}
					else
					{
						// The Binop is of the form const - Temp (Minus does not commute)
						// Translate to $t0 = const - $t1, ($t0)
						Temp newTemp = emitOpConstTemp(op, num, temp);
						munchMoveMemTempDstStm(newTemp, srcExp);
					}
				}
				else
				{
					Temp rhsTemp = munchExp(opRightChild);
					Temp newTemp = emitOpConstTemp(op, num, rhsTemp);
					munchMoveMemTempDstStm(newTemp, srcExp);
				}
			}
			else
			{
				// The left child is neither a const nor a temp
				Temp lhsTemp = null;
				if(leftChildType == AssemExp.CALL) 
					lhsTemp = munchCallExp((AssemCallExp) opLeftChild);
				else if(leftChildType == AssemExp.BIN_OP) 
					lhsTemp = munchBinOpExp((AssemBinOpExp) opLeftChild);
				else if(leftChildType == AssemExp.MEM) 
					lhsTemp = munchMemoryExp((AssemMemory) opLeftChild);
				
				Temp rhsTemp = munchExp(opRightChild);
				Temp newTemp = addOrSubtractTemps(op, lhsTemp, rhsTemp);						
				munchMoveMemTempDstStm(newTemp, srcExp);		
			}
		}
		else
		{
			// The operator is neither plus nor minus
			Temp lhsTemp = munchExp(opLeftChild);
			Temp rhsTemp = munchExp(opRightChild);
			Temp newTemp = opTemps(op,lhsTemp, rhsTemp, null);
			munchMoveMemTempDstStm(newTemp, srcExp);
		}
	}
	
	private Temp opTemps(int op, Temp lhsTemp, Temp rhsTemp, String rhsStr)
	{
		Temp newTemp = new Temp();
		
		// If the third parameter is null, it means that the fourth 
		// parameter is an immediate value
		Integer immediateValue = null;
		if(rhsTemp == null) {
			immediateValue = new Integer(rhsStr);
		}

		// Create the instruction
		int operationCode = 0;
		if(op == AssemBinOpExp.PLUS)
			operationCode = Operation.ADD;
		else if(op == AssemBinOpExp.MINUS)
			operationCode = Operation.MINUS;
		else if(op == AssemBinOpExp.MUL)
			operationCode = Operation.MUL;
		else if(op == AssemBinOpExp.DIV)
			operationCode = Operation.DIV;
		
		Vector destList = new Vector();
		Vector srcList = new Vector();
		destList.addElement(newTemp);
		srcList.addElement(lhsTemp);
		if(rhsTemp != null)
			srcList.addElement(rhsTemp);
		
		//Operation instr = new Operation(resultInstr, destList, srcList, false);
		Operation instr = new Operation(destList, srcList, operationCode, immediateValue, null);
		instructionsList.addElement(instr);
		
		return newTemp;
	}
	
	private Temp emitOpConstTemp(int op, int num, Temp temp)
	{
		Temp constTemp = new Temp();
		
		Vector destList = new Vector();
		destList.addElement(constTemp);
		Operation instr1 = new Operation(destList, null,Operation.LOAD_IMMEDIATE_VALUE,
				 new Integer(num), null);
		
		instructionsList.addElement(instr1);
		return opTemps(op, constTemp, temp, null);
	}
	
	private void munchMoveMemTempDstStm(Temp temp, AssemExp srcExp)
	{
		Temp srcTemp = munchExp(srcExp);

		Vector srcList = new Vector();
		srcList.addElement(srcTemp);    // First element is the source temporary
		srcList.addElement(temp);       // Second element is the temporary that represents the address

		Operation instr = new Operation(null, srcList, Operation.STORE_WORD_INTO_ADDRESS_AT_TEMP,
										new Integer(0), null);
		instructionsList.addElement(instr);
	}
	
	private void munchMoveMemConstDstStm(AssemConst assemConst, AssemExp srcExp)
	{
		Temp srcTemp = munchExp(srcExp);
		
		Vector srcList = new Vector();
		srcList.addElement(srcTemp);
		
		Operation instr = new Operation(null, srcList, Operation.STORE_WORD_INTO_CONST_ADDRESS,
				new Integer(assemConst.getValue()), null);
		instructionsList.addElement(instr);
	}
	
	private void munchMoveMemCallDstStm(AssemCallExp assemCall, AssemExp srcExp)
	{
		String funcName = assemCall.getName();
		String str = tab + "jal " + funcName + newLine;
		Call instr = new Call(funcName, str);
		instructionsList.addElement(instr);
		
		// Instruction for moving $v0 into a new temp
		Temp newTemp = new Temp();
		Temp rhsTemp = Temp.getTemp("v0");
		Vector destList = new Vector();
		destList.addElement(newTemp);
		Vector srcList = new Vector();
		srcList.addElement(rhsTemp);
		Operation moveInstr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,
											null, null);
		instructionsList.addElement(moveInstr);
		
		munchMoveMemTempDstStm(newTemp, srcExp);	
	}
	
	private void munchMoveMemMemDstStm(AssemMemory assemMem, AssemExp srcExp)
	{
		Temp memChildTemp = munchMemoryExp(assemMem);
		munchMoveMemTempDstStm(memChildTemp, srcExp);	
	}
	
	private void munchMoveTempDestStm(Temp temp, AssemExp srcExp)
	{
		int srcType = srcExp.getExpType();
		
		Vector destList = new Vector();
		destList.addElement(temp);
		Vector srcList = new Vector();
		
		if(srcType == AssemExp.MEM)
		{
			AssemMemory assemMem = (AssemMemory) srcExp;
			munchMoveTempMemDestStm(temp, assemMem);
			return;
		}
		
		Operation instr = null;
		if(srcType == AssemExp.BIN_OP)
		{
			AssemBinOpExp assemOp = (AssemBinOpExp) srcExp;
			Temp opTemp = munchBinOpExp(assemOp);
			srcList.addElement(opTemp);
			srcList.addElement(opTemp);
			instr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,  null, null);
		}
		else if(srcType == AssemExp.TEMP)
		{
			AssemTemp tempExp = (AssemTemp) srcExp;
			srcList.addElement(tempExp.getTemp());
			instr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,  null, null);
		}
		else if(srcType == AssemExp.CALL)
		{
			AssemCallExp callExp = (AssemCallExp) srcExp;
			Temp callTemp = munchCallExp(callExp);
			srcList.addElement(callTemp);
			instr = new Operation(destList, srcList,Operation.MOVE_TEMP_TO_TEMP,  null, null);
		}
		else if(srcType == AssemExp.CONST)
		{
			srcList = null;
			AssemConst assemConst = (AssemConst) srcExp;
			instr = new Operation(destList, srcList, Operation.LOAD_IMMEDIATE_VALUE,  
									new Integer(assemConst.getValue()), null);
		}
		else if(srcType == AssemExp.NAME)
		{
			srcList = null;
			AssemName assemName = (AssemName) srcExp;
			instr = new Operation(destList, srcList, Operation.LOAD_ADDRESS, null, assemName.getLabel().toString());
		}
		
		instructionsList.addElement(instr);
	}
	
	private void  munchMoveTempMemDestStm(Temp temp, AssemMemory assemMem)
	{
		AssemExp memChild = assemMem.getMemExp();
		
		int memChildType = memChild.getExpType();
		if(memChildType == AssemExp.BIN_OP)
		{
			AssemBinOpExp binOpExp = (AssemBinOpExp) memChild;
			int opr = binOpExp.getOperator();
			AssemExp leftChild = binOpExp.getLeft();
			AssemExp rightChild = binOpExp.getRight();
			if(opr == AssemBinOpExp.PLUS || opr == AssemBinOpExp.MINUS)
			{
				// This is a plus or minus operator; check further for a tile	
				int leftChildType = leftChild.getExpType();
				int rightChildType = rightChild.getExpType();
				if(leftChildType == AssemExp.TEMP)
				{
					Temp lcTemp = ((AssemTemp) leftChild).getTemp();
					if(rightChildType == AssemExp.CONST)
					{
						// Found a tile -> Move(Temp,(Mem(BinOp(Plus/Minus, Temp, Const)))
						AssemConst assemConst = (AssemConst) rightChild;
						int val = assemConst.getValue();
						String signStr = "";
						if(opr == AssemBinOpExp.MINUS) signStr = "-";
						
						Vector destList = new Vector();
						destList.addElement(temp);
						Vector srcList = new Vector();
						srcList.addElement(lcTemp);
						Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM,
								new Integer(signStr + val), null);
						instructionsList.addElement(instr);
					}
					else
					{
						Temp rhsTemp = munchExp(rightChild);						
						Vector destList = new Vector();
						destList.addElement(temp);
						Vector srcList = new Vector();
						srcList.addElement(rhsTemp);
						Operation instr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP, null, null);
						instructionsList.addElement(instr);
					}
				}
				else if(leftChildType == AssemExp.CONST)
				{
					AssemConst assemConst = (AssemConst) leftChild;
					int val = assemConst.getValue();
					if(opr == AssemBinOpExp.PLUS && rightChildType == AssemExp.TEMP)
					{
						// Found another tile -> Move(Temp,(Mem(BinOp(Plus, Const, Temp)))
						Temp rightTemp = ((AssemTemp)rightChild).getTemp();
						
						Vector destList = new Vector();
						destList.addElement(temp);
						Vector srcList = new Vector();
						srcList.addElement(rightTemp);
						
						Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM,
											new Integer(val), null);
						instructionsList.addElement(instr);
					}
					else
					{
						Temp newTemp = new Temp();
						
						Vector destList1 = new Vector();
						destList1.addElement(newTemp);
						Operation instr = new Operation(destList1, null, Operation.LOAD_IMMEDIATE_VALUE, 
											new Integer(val), null);
						instructionsList.addElement(instr);
						
						Vector destList = new Vector();
						destList.addElement(temp);
						Vector srcList = new Vector();
						srcList.addElement(newTemp);
						Operation instr1 = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,
														 null, null);
						instructionsList.addElement(instr1);
					}
				}
				else
				{
					// The left child is neither temp nor const
					Temp leftTemp = munchExp(leftChild);
					Temp rightTemp = munchExp(rightChild);
					Temp resultTemp = opTemps(opr, leftTemp, rightTemp, null);
					
					Vector destList = new Vector();
					destList.addElement(temp);
					Vector srcList = new Vector();
					srcList.addElement(resultTemp);
					
					Operation instr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,
							 null, null);
					instructionsList.addElement(instr);
				}
			}
			else
			{
				// The operator is neither plus nor minus
				Temp leftTemp = munchExp(leftChild);
				Temp rightTemp = munchExp(rightChild);
				Temp resultTemp = opTemps(opr, leftTemp, rightTemp, null);
				
				Vector destList = new Vector();
				destList.addElement(temp);
				Vector srcList = new Vector();
				srcList.addElement(resultTemp);
				
				Operation instr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP,
						 null, null);
				instructionsList.addElement(instr);
			}
		}
		else
		{
			// The memory expression is not a binary operation.
			Temp memChildTemp = munchExp(memChild);
			
			Vector destList = new Vector();
			destList.addElement(temp);
			Vector srcList = new Vector();
			srcList.addElement(memChildTemp);
			
			Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM,
											new Integer(0), null);
			instructionsList.addElement(instr);
		}
	}
	
	private void  munchCJumpStm(AssemCJump cjumpStm)
	{
		Label ifLabel = cjumpStm.getIfTrueLabel();
		Label elseLabel = cjumpStm.getIfFalseLabel();
		
		AssemExp leftExp = cjumpStm.getLeft();
		AssemExp rightExp = cjumpStm.getRight();
		
		int leftExpType = leftExp.getExpType();
		int rightExpType = rightExp.getExpType();
		int op = cjumpStm.relop;
		int relop = getRelOp(op);
		
		Vector srcList = new Vector();
		String branchOnStr = "";
		AssemblyInstruction instr; 
		if(leftExpType == AssemExp.CONST)
		{
			AssemConst constLeft = (AssemConst) leftExp;
			int value = constLeft.getValue();
			
			if(!(relop == CJump.EQ || relop == CJump.NE))
				relop = AssemCJump.notRel(relop);  // Reverse the condition, since the const is first
			
			Temp rhsTemp = null;
			if(rightExpType == AssemExp.TEMP)
				rhsTemp = ((AssemTemp) rightExp).getTemp();
			else
				rhsTemp = munchExp(rightExp);
			
			srcList.addElement(rhsTemp);
			
			instr = new CJump(srcList, ifLabel.toString(), elseLabel.toString(), relop, new Integer(value));
		}
		else
		{
			Temp lhsTemp = munchExp(leftExp);
			srcList.addElement(lhsTemp);
			if(rightExpType == AssemExp.CONST)
			{
				AssemConst constRight =  (AssemConst) rightExp;
				int val = constRight.getValue();
				instr = new CJump(srcList, ifLabel.toString(), elseLabel.toString(), relop, new Integer(val));
			}
			else
			{
				// Neither the left nor the right are const values
				Temp rhsTemp = munchExp(rightExp);
				srcList.addElement(rhsTemp);
				instr = new CJump(srcList, ifLabel.toString(), elseLabel.toString(), relop, null);
			}
		}
		
		instructionsList.addElement(instr);
	}
	
	/**
	 * For a given relative operator defined in MipsAssemCJump, this function returns the corresponding
	 * relative operation defined in CJump. Both are identical; however, they have been kept separate in
	 * order to achieve modularity.
	 * @param op
	 * @return
	 */
	private int getRelOp(int op){
		switch (op) 
		{
		case AssemCJump.EQ:  return CJump.EQ;
	    case AssemCJump.NE:  return CJump.NE;
		case AssemCJump.LT:  return CJump.LT;
		case AssemCJump.GE:  return CJump.GE;
		case AssemCJump.GT:  return CJump.GT;
		case AssemCJump.LE:  return CJump.LE;
		case AssemCJump.ULT: return CJump.ULT;
		case AssemCJump.UGE: return CJump.UGE;
		case AssemCJump.UGT: return CJump.UGT;
		case AssemCJump.ULE: return CJump.ULE;
		
		default: return -1;
	    }
	}
	
	private void munchJumpStm(AssemJump jumpStm)
	{
		// The expression for the jump must be either a name having a label or an exp 
		// that should be a temp
		AssemExp jmpExp = jumpStm.getLabelExp();
		String jmpStr = "";
		String lblStr = "";
		if(jmpExp.getExpType() == AssemExp.NAME)
		{
			AssemName lblName = (AssemName) jmpExp;
			lblStr = lblName.getLabel().toString();	
			jmpStr = tab + "j " + lblStr + newLine;
		}
		else
		{
			Temp jmpTargetTemp = munchExp(jmpExp);
			lblStr = jmpTargetTemp.toString();
			jmpStr = tab + "j " + lblStr + newLine;
		}
		Jump instr = new Jump(jmpStr, lblStr);
		instructionsList.addElement(instr);
	}

	private void munchLblStm(AssemLabel lblStm)
	{
		String lbl = lblStm.getLabel().toString();
		//String lblStr = "\n" + lbl + ":" + "\n";	
		String lblStr = lbl + ":" + "\n";
		LabelInstr instr = new LabelInstr(lblStr, lbl);
		instructionsList.addElement(instr);
	}

	private Temp munchExp(AssemExp exp)
	{
		Temp ret = new Temp();
		int expType = exp.getExpType();
		if(expType == AssemExp.MEM)
		{
			AssemMemory memExp = (AssemMemory) exp;
			AssemExp memChild = memExp.getMemExp();
			
			int memChildType = memChild.getExpType();
			if(memChildType == AssemExp.BIN_OP)
			{
				AssemBinOpExp binOpExp = (AssemBinOpExp) memChild;
				ret = munchMemBinOpExp(binOpExp);
			}
			else if(memChildType ==  AssemExp.CONST)
			{
				AssemConst constExp = (AssemConst) memChild;
				ret = munchMemConstExp(constExp);
			}
			else if(memChildType ==  AssemExp.CALL)
			{
				AssemCallExp callExp = (AssemCallExp) memChild;
				ret = munchMemCallExp(callExp);
			}
			else if(memChildType == AssemExp.MEM)
			{
				AssemMemory mem = (AssemMemory) memChild;
				ret = munchMemMemExp(mem);
			}
			else if(memChildType == AssemExp.TEMP)
			{
				AssemTemp tempExp = (AssemTemp) memChild;
				ret = munchMemTempExp(tempExp);
			}
		}
		else if(expType == AssemExp.BIN_OP)
		{
			AssemBinOpExp binOpExp = (AssemBinOpExp) exp;
			ret = munchBinOpExp(binOpExp);
		}
		else if(expType == AssemExp.CONST)
		{
			AssemConst constExp = (AssemConst) exp;
			ret = munchConstExp(constExp);
		}
		else if(expType == AssemExp.CALL)
		{
			AssemCallExp callExp = (AssemCallExp) exp;
			ret = munchCallExp(callExp);
		}
		else if(expType == AssemExp.TEMP)
		{
			AssemTemp tempExp = (AssemTemp) exp;
			ret = tempExp.getTemp();
		}
		
		return ret;
	}
	
	private Temp munchMemBinOpExp(AssemBinOpExp binOpExp)
	{
		Temp ret = null;
		AssemExp opLeftChild = binOpExp.getLeft();
		AssemExp opRightChild = binOpExp.getRight();	
		int op = binOpExp.getOperator();
		
		int leftChildType = opLeftChild.getExpType();
		int rightChildType = opRightChild.getExpType();
		
		if(op == AssemBinOpExp.PLUS || op == AssemBinOpExp.MINUS)
		{
			if(leftChildType == AssemExp.TEMP)
			{
				AssemTemp assemTemp = (AssemTemp) opLeftChild;
				Temp temp = assemTemp.getTemp();
				if(opRightChild.getExpType() == AssemExp.CONST)
				{
					// Found a  tile -> Mem(BinOp(Plus/Minus, TEMP, i)), e1)
					AssemConst assemConst = (AssemConst) opRightChild;
					ret = new Temp();
					
					int val = assemConst.getValue();
					if(op == AssemBinOpExp.MINUS) val *= -1;
					
					Vector destList = new Vector();
					Vector srcList = new Vector();
					srcList.addElement(temp);
					destList.addElement(ret);
					Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM,
													new Integer(val), null);
					instructionsList.addElement(instr);
				}
				else
				{
					//	The right child is not a const
					Temp rhsTemp = null;
					if(leftChildType == AssemExp.CALL) 
						rhsTemp = munchCallExp((AssemCallExp) opRightChild);
					else if(leftChildType == AssemExp.BIN_OP) 
						rhsTemp = munchBinOpExp((AssemBinOpExp) opRightChild);
					else if(leftChildType == AssemExp.MEM) 
						rhsTemp = munchMemoryExp((AssemMemory) opRightChild);
					else if(leftChildType == AssemExp.TEMP) 
						rhsTemp = munchTempExp((AssemTemp) opRightChild);
					
					ret = addOrSubtractTemps(op, assemTemp.getTemp(), rhsTemp);							
				}
			}
			else if(leftChildType == AssemExp.CONST)
			{
				AssemConst assemConst = (AssemConst) opLeftChild;
				int num = assemConst.getValue();
				if(rightChildType == AssemExp.TEMP)
				{
					AssemTemp assemTemp = (AssemTemp) opRightChild;
					Temp temp = assemTemp.getTemp();
					if(op == AssemBinOpExp.PLUS )
					{
						// Found a  tile -> Mem(BinOp(Plus, i, TEMP)), e1)								
						ret = new Temp();
						
						Vector destList = new Vector();
						Vector srcList = new Vector();
						srcList.addElement(temp);
						destList.addElement(ret);
						
						int val = assemConst.getValue();
		
						Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM,
														new Integer(val), null);
						instructionsList.addElement(instr);
					}
					else
					{
						// The Binop is of the form "const - Temp" (Minus does not commute)
						// Translate to $t0 = const - $t1, ($t0)
						ret = emitOpConstTemp(op, num, temp);
					}
				}
				else
				{
					Temp rhsTemp = munchExp(opRightChild);
					ret= emitOpConstTemp(op, num, rhsTemp);
				}
			}
			else
			{
				// The left child is neither a const nor a temp
				Temp lhsTemp = null;
				if(leftChildType == AssemExp.CALL) 
					lhsTemp = munchCallExp((AssemCallExp) opLeftChild);
				else if(leftChildType == AssemExp.BIN_OP) 
					lhsTemp = munchBinOpExp((AssemBinOpExp) opLeftChild);
				else if(leftChildType == AssemExp.MEM) 
					lhsTemp = munchMemoryExp((AssemMemory) opLeftChild);
				
				Temp rhsTemp = munchExp(opRightChild);
				ret = addOrSubtractTemps(op, lhsTemp, rhsTemp);							
			}
		}
		else
		{
			// The operator is neither plus nor minus
			Temp lhsTemp = munchExp(opLeftChild);
			Temp rhsTemp = munchExp(opRightChild);
			ret = opTemps(op,lhsTemp, rhsTemp, null);
		}
		return ret;
	}
	
	private Temp munchMemConstExp(AssemConst constExp)
	{
		Temp newTemp = new Temp();
		int val = constExp.getValue();
		
		Vector destList = new Vector();
		destList.addElement(newTemp);
		
		Operation instr = new Operation(destList, null, Operation.LOAD_ADDRESS, new Integer(val), null);
		instructionsList.addElement(instr);
		return newTemp;
	}
	
	private Temp munchMemCallExp(AssemCallExp callExp)
	{
		Temp newTemp = new Temp();
		
		// Instruction to invoke the function
		String funcName = callExp.getName();
		String str = tab + "jal " + funcName + newLine;
		Call instr = new Call(funcName, str);
		instructionsList.addElement(instr);
		
		// Instruction for moving $v0 into the new temp
		Temp rhsTemp = Temp.getTemp("v0");
		Vector destList = new Vector();
		destList.addElement(newTemp);
		Vector srcList = new Vector();
		srcList.addElement(rhsTemp);
		Operation moveInstr = new Operation(destList, srcList, Operation.MOVE_TEMP_TO_TEMP, null, null);
		instructionsList.addElement(moveInstr);
		
		return newTemp;
	}
	
	private Temp munchMemMemExp(AssemMemory mem)
	{
		Temp newTemp = new Temp();
		Temp memTemp = munchMemoryExp(mem);
		
		Vector destList = new Vector();
		Vector srcList = new Vector();
		destList.addElement(newTemp);
		srcList.addElement(memTemp);
		
		Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM, new Integer(0), null);
		instructionsList.addElement(instr);
		return newTemp;
	}
	
	private Temp munchMemTempExp(AssemTemp assemTemp)
	{
		Temp newTemp = new Temp();
		
		Vector destList = new Vector();
		Vector srcList = new Vector();
		destList.addElement(newTemp);
		srcList.addElement(assemTemp);
		
		Operation instr = new Operation(destList, srcList, Operation.LOAD_FROM_TEMP_OFFSET_EXP_MEM, new Integer(0), null);
		instructionsList.addElement(instr);
		return newTemp;
	}
	
	private Temp munchCallExp(AssemCallExp callExp)
	{
		String funcName = callExp.getName();
		String str = tab + "jal " + funcName + newLine;
		Call instr = new Call(funcName, str);
		instructionsList.addElement(instr);

		// TODO - implement call later
		Temp newTemp = Temp.getTemp("v0");
		return newTemp;
	}
	
	private Temp munchConstExp(AssemConst constExp)
	{
		Temp newTemp = new Temp();
		
		Vector destList = new Vector();
		destList.addElement(newTemp);
		Operation instr = new Operation(destList, null, Operation.LOAD_IMMEDIATE_VALUE, 
						new Integer(constExp.getValue()),null);
		instructionsList.addElement(instr);
		return newTemp;
	}
	
	private Temp munchBinOpExp(AssemBinOpExp binOpExp)
	{
		AssemExp leftExp = binOpExp.getLeft();
		AssemExp rightExp = binOpExp.getRight();
		
		int opr = binOpExp.getOperator();
		
		int leftType = leftExp.getExpType();
		int rightType = rightExp.getExpType();
		if(leftType == AssemExp.TEMP)
		{
			AssemTemp leftTemp = (AssemTemp) leftExp;
			if(rightType == AssemExp.CONST)
			{
				int val = ((AssemConst)rightExp).getValue();
				return opTemps(opr, leftTemp.getTemp(), null, "" +val);
			}
		}
		else if(leftType == AssemExp.CONST)
		{
			int val = ((AssemConst)leftExp).getValue();
			if(rightType == AssemExp.TEMP && (opr == AssemBinOpExp.PLUS 
											|| opr == AssemBinOpExp.MINUS))
			{
				Temp tmpRight = ((AssemTemp)rightExp).getTemp();
				return opTemps(opr, tmpRight, null, "" +val);
			}
		}
		
		Temp leftTemp = munchExp(leftExp);
		Temp rightTemp = munchExp(rightExp);
		
		return opTemps(binOpExp.getOperator(), leftTemp, rightTemp, null);
	}
	
	private Temp munchMemoryExp(AssemMemory memExp)
	{
		Temp newTemp = new Temp();
		Vector destList = new Vector();
		Vector srcList = new Vector();
		destList.addElement(newTemp);
		
		AssemExp memChild = memExp.getMemExp();
		int memChildTemp = memChild.getExpType();
		Operation instr;
		if(memChildTemp == AssemExp.MEM)
		{
			Temp memTemp = munchMemoryExp((AssemMemory) memChild);
			srcList.addElement(memTemp);
			instr = new Operation(destList, srcList, Operation.LOAD_ADDRESS, null, null);
		}
		else if(memChildTemp == AssemExp.CONST)
		{
			AssemConst assemConst = (AssemConst) memChild;
			int val = assemConst.getValue();
			instr = new Operation(destList, srcList, Operation.LOAD_ADDRESS, new Integer(val), null);
		}
		else if(memChildTemp == AssemExp.CALL)
		{
			Temp callTemp = munchCallExp((AssemCallExp) memChild);
			srcList.addElement(callTemp);
			instr = new Operation(destList, srcList, Operation.LOAD_ADDRESS, null, null);
		}
		else
		{
			// Must be temp
			AssemTemp assemTemp = (AssemTemp) memChild;
			srcList.addElement(assemTemp.getTemp());
			instr = new Operation(destList, srcList, Operation.LOAD_ADDRESS, null, null);

		}
		instructionsList.addElement(instr);
		return newTemp;
	}

	private Temp munchTempExp(AssemTemp tempExp)
	{
		return tempExp.getTemp();
	}
	
	private Temp addOrSubtractTemps(int op, Temp lhsTemp, Temp rhsTemp)
	{
		Temp newTemp = new Temp();
		int oprCode = Operation.ADD;
		if(op != AssemBinOpExp.PLUS)
			oprCode = Operation.MINUS;
		
		Vector destList = new Vector();
		Vector srcList = new Vector();
		destList.addElement(newTemp);
		srcList.addElement(lhsTemp);
		srcList.addElement(rhsTemp);
		
		Operation instr = new Operation(destList, srcList, oprCode, new Integer(0), null);
		instructionsList.addElement(instr);
		return newTemp;
	}
	
	private void emitMoveMemOpTempConst(AssemExp srcExp, int signNum, 
						int num, Temp temp)
	{
		Temp srcTemp = munchExp(srcExp);
		
		Vector srcList = new Vector();
		srcList.addElement(temp);
		srcList.addElement(srcTemp);
		
		if(signNum == AssemBinOpExp.MINUS) num *= -1;
		
		Operation instr = new Operation(null, srcList, Operation.STORE_WORD_INTO_ADDRESS_AT_TEMP,
				new Integer(num), null);
		instructionsList.addElement(instr);
	}
	
	public Vector getInstructionsList()
	{
		return instructionsList;
	}
}
